{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "26f25790-3118-4147-a61a-81776717c94a",
   "metadata": {},
   "source": [
    "# Wind Turbine Digital twin\n",
    "* working with MLFlow"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "c42cb9e5-3e12-4c48-b509-285a158a3bf3",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Collecting mlflow\n",
      "  Downloading mlflow-2.16.0-py3-none-any.whl.metadata (29 kB)\n",
      "Collecting mlflow-skinny==2.16.0 (from mlflow)\n",
      "  Downloading mlflow_skinny-2.16.0-py3-none-any.whl.metadata (30 kB)\n",
      "Collecting Flask<4 (from mlflow)\n",
      "  Downloading flask-3.0.3-py3-none-any.whl.metadata (3.2 kB)\n",
      "Requirement already satisfied: alembic!=1.10.0,<2 in /opt/conda/lib/python3.11/site-packages (from mlflow) (1.12.0)\n",
      "Collecting docker<8,>=4.0.0 (from mlflow)\n",
      "  Downloading docker-7.1.0-py3-none-any.whl.metadata (3.8 kB)\n",
      "Collecting graphene<4 (from mlflow)\n",
      "  Downloading graphene-3.3-py2.py3-none-any.whl.metadata (7.7 kB)\n",
      "Collecting markdown<4,>=3.3 (from mlflow)\n",
      "  Downloading Markdown-3.7-py3-none-any.whl.metadata (7.0 kB)\n",
      "Requirement already satisfied: matplotlib<4 in /opt/conda/lib/python3.11/site-packages (from mlflow) (3.8.0)\n",
      "Requirement already satisfied: numpy<3 in /opt/conda/lib/python3.11/site-packages (from mlflow) (1.24.4)\n",
      "Requirement already satisfied: pandas<3 in /opt/conda/lib/python3.11/site-packages (from mlflow) (2.1.1)\n",
      "Requirement already satisfied: pyarrow<18,>=4.0.0 in /opt/conda/lib/python3.11/site-packages (from mlflow) (13.0.0)\n",
      "Requirement already satisfied: scikit-learn<2 in /opt/conda/lib/python3.11/site-packages (from mlflow) (1.3.1)\n",
      "Requirement already satisfied: scipy<2 in /opt/conda/lib/python3.11/site-packages (from mlflow) (1.11.3)\n",
      "Requirement already satisfied: sqlalchemy<3,>=1.4.0 in /opt/conda/lib/python3.11/site-packages (from mlflow) (2.0.22)\n",
      "Requirement already satisfied: Jinja2<4,>=2.11 in /opt/conda/lib/python3.11/site-packages (from mlflow) (3.1.2)\n",
      "Collecting gunicorn<24 (from mlflow)\n",
      "  Downloading gunicorn-23.0.0-py3-none-any.whl.metadata (4.4 kB)\n",
      "Collecting cachetools<6,>=5.0.0 (from mlflow-skinny==2.16.0->mlflow)\n",
      "  Downloading cachetools-5.5.0-py3-none-any.whl.metadata (5.3 kB)\n",
      "Requirement already satisfied: click<9,>=7.0 in /opt/conda/lib/python3.11/site-packages (from mlflow-skinny==2.16.0->mlflow) (8.1.7)\n",
      "Requirement already satisfied: cloudpickle<4 in /opt/conda/lib/python3.11/site-packages (from mlflow-skinny==2.16.0->mlflow) (3.0.0)\n",
      "Collecting databricks-sdk<1,>=0.20.0 (from mlflow-skinny==2.16.0->mlflow)\n",
      "  Downloading databricks_sdk-0.32.0-py3-none-any.whl.metadata (37 kB)\n",
      "Requirement already satisfied: gitpython<4,>=3.1.9 in /opt/conda/lib/python3.11/site-packages (from mlflow-skinny==2.16.0->mlflow) (3.1.40)\n",
      "Requirement already satisfied: importlib-metadata!=4.7.0,<9,>=3.7.0 in /opt/conda/lib/python3.11/site-packages (from mlflow-skinny==2.16.0->mlflow) (6.8.0)\n",
      "Collecting opentelemetry-api<3,>=1.9.0 (from mlflow-skinny==2.16.0->mlflow)\n",
      "  Downloading opentelemetry_api-1.27.0-py3-none-any.whl.metadata (1.4 kB)\n",
      "Collecting opentelemetry-sdk<3,>=1.9.0 (from mlflow-skinny==2.16.0->mlflow)\n",
      "  Downloading opentelemetry_sdk-1.27.0-py3-none-any.whl.metadata (1.5 kB)\n",
      "Requirement already satisfied: packaging<25 in /opt/conda/lib/python3.11/site-packages (from mlflow-skinny==2.16.0->mlflow) (23.2)\n",
      "Requirement already satisfied: protobuf<6,>=3.12.0 in /opt/conda/lib/python3.11/site-packages (from mlflow-skinny==2.16.0->mlflow) (4.24.3)\n",
      "Requirement already satisfied: pyyaml<7,>=5.1 in /opt/conda/lib/python3.11/site-packages (from mlflow-skinny==2.16.0->mlflow) (6.0.1)\n",
      "Requirement already satisfied: requests<3,>=2.17.3 in /opt/conda/lib/python3.11/site-packages (from mlflow-skinny==2.16.0->mlflow) (2.31.0)\n",
      "Collecting sqlparse<1,>=0.4.0 (from mlflow-skinny==2.16.0->mlflow)\n",
      "  Downloading sqlparse-0.5.1-py3-none-any.whl.metadata (3.9 kB)\n",
      "Requirement already satisfied: Mako in /opt/conda/lib/python3.11/site-packages (from alembic!=1.10.0,<2->mlflow) (1.2.4)\n",
      "Requirement already satisfied: typing-extensions>=4 in /opt/conda/lib/python3.11/site-packages (from alembic!=1.10.0,<2->mlflow) (4.8.0)\n",
      "Requirement already satisfied: urllib3>=1.26.0 in /opt/conda/lib/python3.11/site-packages (from docker<8,>=4.0.0->mlflow) (2.0.7)\n",
      "Collecting Werkzeug>=3.0.0 (from Flask<4->mlflow)\n",
      "  Downloading werkzeug-3.0.4-py3-none-any.whl.metadata (3.7 kB)\n",
      "Collecting itsdangerous>=2.1.2 (from Flask<4->mlflow)\n",
      "  Downloading itsdangerous-2.2.0-py3-none-any.whl.metadata (1.9 kB)\n",
      "Requirement already satisfied: blinker>=1.6.2 in /opt/conda/lib/python3.11/site-packages (from Flask<4->mlflow) (1.6.3)\n",
      "Collecting graphql-core<3.3,>=3.1 (from graphene<4->mlflow)\n",
      "  Downloading graphql_core-3.2.4-py3-none-any.whl.metadata (10 kB)\n",
      "Collecting graphql-relay<3.3,>=3.1 (from graphene<4->mlflow)\n",
      "  Downloading graphql_relay-3.2.0-py3-none-any.whl.metadata (12 kB)\n",
      "Collecting aniso8601<10,>=8 (from graphene<4->mlflow)\n",
      "  Downloading aniso8601-9.0.1-py2.py3-none-any.whl.metadata (23 kB)\n",
      "Requirement already satisfied: MarkupSafe>=2.0 in /opt/conda/lib/python3.11/site-packages (from Jinja2<4,>=2.11->mlflow) (2.1.3)\n",
      "Requirement already satisfied: contourpy>=1.0.1 in /opt/conda/lib/python3.11/site-packages (from matplotlib<4->mlflow) (1.1.1)\n",
      "Requirement already satisfied: cycler>=0.10 in /opt/conda/lib/python3.11/site-packages (from matplotlib<4->mlflow) (0.12.1)\n",
      "Requirement already satisfied: fonttools>=4.22.0 in /opt/conda/lib/python3.11/site-packages (from matplotlib<4->mlflow) (4.43.1)\n",
      "Requirement already satisfied: kiwisolver>=1.0.1 in /opt/conda/lib/python3.11/site-packages (from matplotlib<4->mlflow) (1.4.5)\n",
      "Requirement already satisfied: pillow>=6.2.0 in /opt/conda/lib/python3.11/site-packages (from matplotlib<4->mlflow) (10.1.0)\n",
      "Requirement already satisfied: pyparsing>=2.3.1 in /opt/conda/lib/python3.11/site-packages (from matplotlib<4->mlflow) (3.1.1)\n",
      "Requirement already satisfied: python-dateutil>=2.7 in /opt/conda/lib/python3.11/site-packages (from matplotlib<4->mlflow) (2.8.2)\n",
      "Requirement already satisfied: pytz>=2020.1 in /opt/conda/lib/python3.11/site-packages (from pandas<3->mlflow) (2023.3.post1)\n",
      "Requirement already satisfied: tzdata>=2022.1 in /opt/conda/lib/python3.11/site-packages (from pandas<3->mlflow) (2023.3)\n",
      "Requirement already satisfied: joblib>=1.1.1 in /opt/conda/lib/python3.11/site-packages (from scikit-learn<2->mlflow) (1.3.2)\n",
      "Requirement already satisfied: threadpoolctl>=2.0.0 in /opt/conda/lib/python3.11/site-packages (from scikit-learn<2->mlflow) (3.2.0)\n",
      "Requirement already satisfied: greenlet!=0.4.17 in /opt/conda/lib/python3.11/site-packages (from sqlalchemy<3,>=1.4.0->mlflow) (3.0.0)\n",
      "Collecting google-auth~=2.0 (from databricks-sdk<1,>=0.20.0->mlflow-skinny==2.16.0->mlflow)\n",
      "  Downloading google_auth-2.34.0-py2.py3-none-any.whl.metadata (4.7 kB)\n",
      "Requirement already satisfied: gitdb<5,>=4.0.1 in /opt/conda/lib/python3.11/site-packages (from gitpython<4,>=3.1.9->mlflow-skinny==2.16.0->mlflow) (4.0.10)\n",
      "Requirement already satisfied: zipp>=0.5 in /opt/conda/lib/python3.11/site-packages (from importlib-metadata!=4.7.0,<9,>=3.7.0->mlflow-skinny==2.16.0->mlflow) (3.17.0)\n",
      "Collecting deprecated>=1.2.6 (from opentelemetry-api<3,>=1.9.0->mlflow-skinny==2.16.0->mlflow)\n",
      "  Downloading Deprecated-1.2.14-py2.py3-none-any.whl.metadata (5.4 kB)\n",
      "Collecting opentelemetry-semantic-conventions==0.48b0 (from opentelemetry-sdk<3,>=1.9.0->mlflow-skinny==2.16.0->mlflow)\n",
      "  Downloading opentelemetry_semantic_conventions-0.48b0-py3-none-any.whl.metadata (2.4 kB)\n",
      "Requirement already satisfied: six>=1.5 in /opt/conda/lib/python3.11/site-packages (from python-dateutil>=2.7->matplotlib<4->mlflow) (1.16.0)\n",
      "Requirement already satisfied: charset-normalizer<4,>=2 in /opt/conda/lib/python3.11/site-packages (from requests<3,>=2.17.3->mlflow-skinny==2.16.0->mlflow) (3.3.0)\n",
      "Requirement already satisfied: idna<4,>=2.5 in /opt/conda/lib/python3.11/site-packages (from requests<3,>=2.17.3->mlflow-skinny==2.16.0->mlflow) (3.4)\n",
      "Requirement already satisfied: certifi>=2017.4.17 in /opt/conda/lib/python3.11/site-packages (from requests<3,>=2.17.3->mlflow-skinny==2.16.0->mlflow) (2023.7.22)\n",
      "Collecting wrapt<2,>=1.10 (from deprecated>=1.2.6->opentelemetry-api<3,>=1.9.0->mlflow-skinny==2.16.0->mlflow)\n",
      "  Downloading wrapt-1.16.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl.metadata (6.6 kB)\n",
      "Requirement already satisfied: smmap<6,>=3.0.1 in /opt/conda/lib/python3.11/site-packages (from gitdb<5,>=4.0.1->gitpython<4,>=3.1.9->mlflow-skinny==2.16.0->mlflow) (3.0.5)\n",
      "Collecting pyasn1-modules>=0.2.1 (from google-auth~=2.0->databricks-sdk<1,>=0.20.0->mlflow-skinny==2.16.0->mlflow)\n",
      "  Downloading pyasn1_modules-0.4.0-py3-none-any.whl.metadata (3.4 kB)\n",
      "Collecting rsa<5,>=3.1.4 (from google-auth~=2.0->databricks-sdk<1,>=0.20.0->mlflow-skinny==2.16.0->mlflow)\n",
      "  Downloading rsa-4.9-py3-none-any.whl.metadata (4.2 kB)\n",
      "Collecting pyasn1<0.7.0,>=0.4.6 (from pyasn1-modules>=0.2.1->google-auth~=2.0->databricks-sdk<1,>=0.20.0->mlflow-skinny==2.16.0->mlflow)\n",
      "  Downloading pyasn1-0.6.0-py2.py3-none-any.whl.metadata (8.3 kB)\n",
      "Downloading mlflow-2.16.0-py3-none-any.whl (26.6 MB)\n",
      "\u001b[2K   \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m26.6/26.6 MB\u001b[0m \u001b[31m7.4 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m00:01\u001b[0m00:01\u001b[0m\n",
      "\u001b[?25hDownloading mlflow_skinny-2.16.0-py3-none-any.whl (5.6 MB)\n",
      "\u001b[2K   \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m5.6/5.6 MB\u001b[0m \u001b[31m8.3 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m00:01\u001b[0m00:01\u001b[0m\n",
      "\u001b[?25hDownloading docker-7.1.0-py3-none-any.whl (147 kB)\n",
      "\u001b[2K   \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m147.8/147.8 kB\u001b[0m \u001b[31m1.3 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0ma \u001b[36m0:00:01\u001b[0m\n",
      "\u001b[?25hDownloading flask-3.0.3-py3-none-any.whl (101 kB)\n",
      "\u001b[2K   \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m101.7/101.7 kB\u001b[0m \u001b[31m3.7 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
      "\u001b[?25hDownloading graphene-3.3-py2.py3-none-any.whl (128 kB)\n",
      "\u001b[2K   \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m128.2/128.2 kB\u001b[0m \u001b[31m5.5 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
      "\u001b[?25hDownloading gunicorn-23.0.0-py3-none-any.whl (85 kB)\n",
      "\u001b[2K   \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m85.0/85.0 kB\u001b[0m \u001b[31m3.8 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
      "\u001b[?25hDownloading Markdown-3.7-py3-none-any.whl (106 kB)\n",
      "\u001b[2K   \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m106.3/106.3 kB\u001b[0m \u001b[31m4.7 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
      "\u001b[?25hDownloading aniso8601-9.0.1-py2.py3-none-any.whl (52 kB)\n",
      "\u001b[2K   \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m52.8/52.8 kB\u001b[0m \u001b[31m3.8 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
      "\u001b[?25hDownloading cachetools-5.5.0-py3-none-any.whl (9.5 kB)\n",
      "Downloading databricks_sdk-0.32.0-py3-none-any.whl (551 kB)\n",
      "\u001b[2K   \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m552.0/552.0 kB\u001b[0m \u001b[31m7.5 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m00:01\u001b[0m00:01\u001b[0m\n",
      "\u001b[?25hDownloading graphql_core-3.2.4-py3-none-any.whl (203 kB)\n",
      "\u001b[2K   \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m203.2/203.2 kB\u001b[0m \u001b[31m3.6 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0ma \u001b[36m0:00:01\u001b[0m\n",
      "\u001b[?25hDownloading graphql_relay-3.2.0-py3-none-any.whl (16 kB)\n",
      "Downloading itsdangerous-2.2.0-py3-none-any.whl (16 kB)\n",
      "Downloading opentelemetry_api-1.27.0-py3-none-any.whl (63 kB)\n",
      "\u001b[2K   \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m64.0/64.0 kB\u001b[0m \u001b[31m4.5 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
      "\u001b[?25hDownloading opentelemetry_sdk-1.27.0-py3-none-any.whl (110 kB)\n",
      "\u001b[2K   \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m110.5/110.5 kB\u001b[0m \u001b[31m5.2 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
      "\u001b[?25hDownloading opentelemetry_semantic_conventions-0.48b0-py3-none-any.whl (149 kB)\n",
      "\u001b[2K   \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m149.7/149.7 kB\u001b[0m \u001b[31m6.5 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
      "\u001b[?25hDownloading sqlparse-0.5.1-py3-none-any.whl (44 kB)\n",
      "\u001b[2K   \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m44.2/44.2 kB\u001b[0m \u001b[31m3.8 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
      "\u001b[?25hDownloading werkzeug-3.0.4-py3-none-any.whl (227 kB)\n",
      "\u001b[2K   \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m227.6/227.6 kB\u001b[0m \u001b[31m6.2 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m00:01\u001b[0m\n",
      "\u001b[?25hDownloading Deprecated-1.2.14-py2.py3-none-any.whl (9.6 kB)\n",
      "Downloading google_auth-2.34.0-py2.py3-none-any.whl (200 kB)\n",
      "\u001b[2K   \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m200.9/200.9 kB\u001b[0m \u001b[31m4.6 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0ma \u001b[36m0:00:01\u001b[0m\n",
      "\u001b[?25hDownloading pyasn1_modules-0.4.0-py3-none-any.whl (181 kB)\n",
      "\u001b[2K   \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m181.2/181.2 kB\u001b[0m \u001b[31m7.5 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
      "\u001b[?25hDownloading rsa-4.9-py3-none-any.whl (34 kB)\n",
      "Downloading wrapt-1.16.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl (80 kB)\n",
      "\u001b[2K   \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m80.7/80.7 kB\u001b[0m \u001b[31m6.1 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
      "\u001b[?25hDownloading pyasn1-0.6.0-py2.py3-none-any.whl (85 kB)\n",
      "\u001b[2K   \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m85.3/85.3 kB\u001b[0m \u001b[31m4.3 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
      "\u001b[?25hInstalling collected packages: aniso8601, wrapt, Werkzeug, sqlparse, pyasn1, markdown, itsdangerous, gunicorn, graphql-core, cachetools, rsa, pyasn1-modules, graphql-relay, Flask, docker, deprecated, opentelemetry-api, graphene, google-auth, opentelemetry-semantic-conventions, databricks-sdk, opentelemetry-sdk, mlflow-skinny, mlflow\n",
      "Successfully installed Flask-3.0.3 Werkzeug-3.0.4 aniso8601-9.0.1 cachetools-5.5.0 databricks-sdk-0.32.0 deprecated-1.2.14 docker-7.1.0 google-auth-2.34.0 graphene-3.3 graphql-core-3.2.4 graphql-relay-3.2.0 gunicorn-23.0.0 itsdangerous-2.2.0 markdown-3.7 mlflow-2.16.0 mlflow-skinny-2.16.0 opentelemetry-api-1.27.0 opentelemetry-sdk-1.27.0 opentelemetry-semantic-conventions-0.48b0 pyasn1-0.6.0 pyasn1-modules-0.4.0 rsa-4.9 sqlparse-0.5.1 wrapt-1.16.0\n"
     ]
    }
   ],
   "source": [
    "!rm -rf wind_turbine\n",
    "!pip install mlflow"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "09eedb9d-78b7-4776-8dc7-db7c565ba251",
   "metadata": {},
   "source": [
    "## Loading data"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "8453d1e4-a5c0-48f4-a75e-25d1bc8d4e30",
   "metadata": {},
   "outputs": [],
   "source": [
    "import pandas as pd\n",
    "df = pd.read_csv(\"wind_turbine.csv\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "91f65be4-99d3-4f94-b237-dae2f5070e43",
   "metadata": {},
   "source": [
    "## EDA"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "3c703afb-a484-4350-b625-f927352582ac",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "Index(['wind_speed_ms', 'power_generated_kw'], dtype='object')"
      ]
     },
     "execution_count": 3,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "df.columns"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "id": "80597a6a-1f87-47be-ac9c-3903e65e0333",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<Axes: xlabel='wind_speed_ms', ylabel='power_generated_kw'>"
      ]
     },
     "execution_count": 4,
     "metadata": {},
     "output_type": "execute_result"
    },
    {
     "data": {
      "image/png": "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",
      "text/plain": [
       "<Figure size 640x480 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "df.plot.scatter(x='wind_speed_ms', y='power_generated_kw')"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "135bdc2e-7784-4220-a80f-a96cff11e21e",
   "metadata": {},
   "source": [
    "## Model Development"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "id": "78a8f2d9-2bc1-43a3-a196-f95ed6262408",
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "2024/09/05 18:54:05 WARNING mlflow.sklearn: Failed to log training dataset information to MLflow Tracking. Reason: 'Series' object has no attribute 'flatten'\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "0.9362696939018997\n",
      "Registering the model via MLFlow\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "2024/09/05 18:54:16 WARNING mlflow.models.model: Model logged without a signature and input example. Please set `input_example` parameter when logging the model to auto infer the model signature.\n",
      "Successfully registered model 'wind_turbine'.\n",
      "2024/09/05 18:54:17 INFO mlflow.store.model_registry.abstract_store: Waiting up to 300 seconds for model version to finish creation. Model name: wind_turbine, version 1\n",
      "Created version '1' of model 'wind_turbine'.\n",
      "2024/09/05 18:54:20 INFO mlflow.tracking._tracking_service.client: 🏃 View run aged-cub-946 at: http://mlflow_server:5000/#/experiments/0/runs/6691f17dd4ab499cb71010251e0b80dd.\n",
      "2024/09/05 18:54:20 INFO mlflow.tracking._tracking_service.client: 🧪 View experiment at: http://mlflow_server:5000/#/experiments/0.\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Run ID: 6691f17dd4ab499cb71010251e0b80dd\n"
     ]
    }
   ],
   "source": [
    "import pandas as pd\n",
    "import mlflow, os\n",
    "import mlflow.sklearn\n",
    "from sklearn.ensemble import GradientBoostingRegressor\n",
    "from sklearn.metrics import r2_score\n",
    "from sklearn.model_selection import train_test_split\n",
    "from datetime import datetime\n",
    "\n",
    "registered_model_name=\"wind_turbine\"\n",
    "n_estimators=100\n",
    "\n",
    "# Start Logging\n",
    "with mlflow.start_run() as run:\n",
    "    # enable autologging\n",
    "    mlflow.sklearn.autolog()\n",
    "    mlflow.log_metric(\"num_samples\", df.shape[0])\n",
    "    mlflow.log_metric(\"num_features\", df.shape[1] - 1)\n",
    "    \n",
    "    #Split train and test datasets\n",
    "    train_df, test_df = train_test_split(\n",
    "        df,\n",
    "        test_size=0.3,\n",
    "    )\n",
    "\n",
    "    # build X,y and train,test datasets \n",
    "    y_train = train_df.pop(\"power_generated_kw\")\n",
    "    X_train = train_df.values\n",
    "    y_test = test_df.pop(\"power_generated_kw\")\n",
    "    X_test = test_df.values\n",
    "\n",
    "    # build the model\n",
    "    reg = GradientBoostingRegressor(\n",
    "        n_estimators=n_estimators\n",
    "    )\n",
    "    reg.fit(X_train, y_train)\n",
    "\n",
    "    # test the model\n",
    "    y_pred = reg.predict(X_test)\n",
    "    \n",
    "    print(r2_score(y_test, y_pred))\n",
    "    mlflow.log_metric(\"r2_score_test\", r2_score(y_test, y_pred))\n",
    "\n",
    "\n",
    "    # Registering the model to the workspace\n",
    "    print(\"Registering the model via MLFlow\")\n",
    "    mlflow.sklearn.log_model(\n",
    "        sk_model=reg,\n",
    "        registered_model_name=registered_model_name,\n",
    "        artifact_path=registered_model_name,\n",
    "    )\n",
    "    \n",
    "    # Saving the model to a file\n",
    "    mlflow.sklearn.save_model(\n",
    "        sk_model=reg,\n",
    "        path=os.path.join(registered_model_name, \"trained_model\")\n",
    "    )\n",
    "\n",
    "    # Print the run id\n",
    "    run_id = run.info.run_id\n",
    "    print(f\"Run ID: {run.info.run_id}\")\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "id": "16bdee14-b8d6-4880-94f5-2a8e978c98f7",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Server the model with:\n",
      " mlflow models serve -m models:/wind_turbine/1 -p 5001\n"
     ]
    },
    {
     "data": {
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "441099fe153a4c668b62a2386d9aa69d",
       "version_major": 2,
       "version_minor": 0
      },
      "text/plain": [
       "Downloading artifacts:   0%|          | 0/5 [00:00<?, ?it/s]"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "text/plain": [
       "array([100.42300109,  80.97644646,  70.50022471, ...,   2.57685424,\n",
       "        14.22387276,  41.2902596 ])"
      ]
     },
     "execution_count": 6,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "import mlflow\n",
    "logged_model = f'runs:/{run_id}/wind_turbine'\n",
    "logged_model = f'models:/wind_turbine/1'\n",
    "print(f\"Server the model with:\\n mlflow models serve -m {logged_model} -p 5001\")\n",
    "\n",
    "# Load model as a PyFuncModel.\n",
    "loaded_model = mlflow.pyfunc.load_model(logged_model)\n",
    "\n",
    "# Predict on a Pandas DataFrame.\n",
    "import pandas as pd\n",
    "loaded_model.predict(X_test)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "id": "abef3989-5f08-4427-84a2-5225578fe32a",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Overwriting main.py\n"
     ]
    }
   ],
   "source": [
    "%%writefile main.py\n",
    "from fastapi import FastAPI\n",
    "import mlflow\n",
    "import pandas as pd\n",
    "import numpy as np\n",
    "\n",
    "app = FastAPI()\n",
    "@app.post(\"/predict\")\n",
    "async def predict(X_test:float) -> list:\n",
    "    logged_model = f'models:/wind_turbine/1'\n",
    "    \n",
    "    loaded_model = mlflow.pyfunc.load_model(logged_model)\n",
    "    return list(loaded_model.predict(np.array([X_test]).reshape(-1, 1)))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "d6102634-bd27-4a5c-ac94-078c9536026a",
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "2de9880c-2d9f-4a58-84c5-08ede19167de",
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.11.6"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
